package com.ht.api.util;

import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.LongAdder;

import javax.sql.DataSource;

import com.ht.api.constant.CacheConstants;
import com.ht.api.db.Db;
import com.ht.api.db.DbConfig;
import com.ht.api.db.Entity;
import com.ht.api.db.util.RedisUtil;
import com.ht.api.open.OpenUtil;

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

/**
 * 多线程打标工具类
 * @author asq
 * @createTime 2024年12月9日 13:47:47
 */
@Slf4j
public class HtTagUtil {
	/** 打标签的接口ID */
	private static final long APIID = 1896150837936394240L;
	
	/** 默认重试次数 */
	private static final int DEFAULT_RETRY = 10;
	
	/** 统计接口各个返回码的总数 */
	private static final ConcurrentHashMap<Integer, LongAdder> map = new ConcurrentHashMap<>();
	
    /** 用于控制暂停状态的标志 */
    private static final AtomicBoolean isPaused = new AtomicBoolean(false);
	
	/**
	 * 打标参数
	 * @author asq
	 * @createTime 2025年3月3日 22:26:10
	 */
	@Data
	@Accessors(chain = true)
	@Builder
	@AllArgsConstructor
	public static class TagBean{
		/** 应用ID */
		private String appId;
		/** 客服ID */
		private String userid;
		/** 外部用户ID */
		private String externalUserid;
		/** 标签ID */
		private JSONArray tags;
	}
	
	/**
	 * 开始打标
	 * @param taskList 要打标的数据列表
	 */
	public static void tag(List<TagBean> taskList) {
		tag(taskList, null, DEFAULT_RETRY);
	}
	
	/**
	 * 开始打标
	 * @param taskList 要打标的数据列表
	 * @param retry 打标失败时的重试次数
	 */
	public static void tag(List<TagBean> taskList, int retry) {
		tag(taskList, null, retry);
	}
	
	/**
	 * 开始打标
	 * @param taskList 要打标的数据列表
	 * @param callback 打标完后的回调
	 */
	public static void tag(List<TagBean> taskList, Runnable callback) {
		tag(taskList, callback, DEFAULT_RETRY);
	}
	
	/**
	 * 开始打标
	 * @param taskList 要打标的数据列表
	 * @param callback 打标完后的回调
	 * @param retry 打标失败时的重试次数
	 */
	public static void tag(List<TagBean> taskList, Runnable callback, int retry) {
	    long time = System.currentTimeMillis();
	    ExecutorService executor = Executors.newFixedThreadPool(50);

	    // 初始化计数器
	    CountDownLatch latch = new CountDownLatch(taskList.size());

	    // 提交任务并减少计数器
	    taskList.forEach(tagBean -> {
	        executor.submit(() -> {
	        	doTagTask(latch, tagBean, retry, true);
	        });
	    });

	    // 如果有回调，则开一个线程，等待所有任务完成后触发回调
	    if (callback != null) {
	        executor.submit(() -> {
	            try {
	                latch.await(); // 等待所有任务完成
	                log.info("[HtTaskMinExecutor] 全部任务完成，打标总数：{}，耗时：{}ms", taskList.size(),
	                		(System.currentTimeMillis() - time));
	                log.info("计数统计：{}", JSONUtil.toJsonPrettyStr(map));
	                callback.run(); // 触发回调
	            } catch (InterruptedException e) {
	                Thread.currentThread().interrupt(); // 恢复中断状态
	                log.error("等待任务完成时被中断", e);
	            }
	        });
	    }

	    // 关闭线程池
	    executor.shutdown();
	}
	
	/**
	 * 执行打标任务
	 * @param latch 计数器
	 * @param tagBean 打标参数
	 * @param retry 重试次数
	 * @param isFirstCall 是否为首次调用，首次调用需要统计错误码
	 * @return
	 */
	private static String doTagTask(CountDownLatch latch, TagBean tagBean, int retry, boolean isFirstCall) {
		try {
			// 调用企微接口打标
			String str = doTag(tagBean);
			
			// 检查接口返回的结果
			JSONObject result = JSONUtil.parseObj(str);
            int errcode = result.getInt("errcode");
            if (isFirstCall) statis(errcode);
			
            // 处理45009/45033错误码 - 频率限制
            if (errcode == 45009 || errcode == 45033) {
                // 尝试获取暂停状态的控制权
                if (isPaused.compareAndSet(false, true)) {
                    try {
                        log.warn("检测到频率限制(errcode=45009)，所有线程暂停...");
                        //ApiConfig.disableUrl(APIID); // 暂停此接口的后续访问
                        Thread.sleep(20000); // 暂停
                    } finally {
                        synchronized (isPaused) {
                            isPaused.set(false);
                            //ApiConfig.enableUrl(APIID); // 恢复此接口的后续访问
                            isPaused.notifyAll(); // 唤醒所有等待的线程
                        }
                        log.info("暂停结束，继续执行...");
                    }
                } else {
                    // 已经有其他线程设置了暂停状态，等待恢复
                    synchronized (isPaused) {
                        if (isPaused.get()) {  // 再次检查，避免竞态条件
                            isPaused.wait();  // 直接等待，不需要循环
                        }
                    }
                }
                // 重试当前任务
                return retry(latch, tagBean, retry, str);
            }
			
			if (errcode == 0) {
				// 任务成功完成后减少计数器
				latch.countDown(); 
				return str;
			} else {
				return retry(latch, tagBean, retry, str);
			}
		} catch (Exception e) {
			return retry(latch, tagBean, retry, e.getMessage());
		}
	}
	
	/**
	 * 统计错误码次数
	 * @param errcode
	 */
	private static void statis(int errcode) {
	    map.computeIfAbsent(errcode, k -> new LongAdder()).increment();
	}
	
	/**
	 * 任务执行失败时重试
	 * @param latch 计数器
	 * @param tagBean 打标参数
	 * @param retry 重试次数
	 * @param errMsg 失败原因
	 * @return
	 */
	private static String retry(CountDownLatch latch, TagBean tagBean, int retry, String errMsg) {
		if(retry > 0) {
			log.info("[HtTaskMinExecutor]任务执行发生异常，还有{}次重试机会~", retry);
			
			// 短暂休眠，再重试
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			return doTagTask(latch, tagBean, --retry, false);
		} else {
			// 重试次数已完，需要减少计数器
			latch.countDown();
			
    		// 执行失败，记录到数据库
    		saveFailLog(tagBean, errMsg);
			
			return errMsg;
		}
	}
	
	/**
	 * 调用企微接口打标
	 * @param tagBean 打标参数
	 * @return
	 */
	private static String doTag(TagBean tagBean) {
		// 构造打标接口的参数
		JSONObject param = JSONUtil.createObj()
				.set("userid", tagBean.getUserid())
				.set("external_userid", tagBean.getExternalUserid())
				.set("add_tag", tagBean.getTags());
		
		// 调用企微接口打标
		return OpenUtil.call(tagBean.getAppId(), APIID, param);
	}
	
	/**
	 * 打标失败时记录失败原因到数据库
	 */
	private static void saveFailLog(TagBean tagBean, String errMsg) {
		// 保存打标失败的日志
		Entity tagMarkLog = Db.createEntity("tag_mark_log")
				.set("appid", tagBean.getAppId())
				.set("apiId", APIID)
				.set("userid", tagBean.getUserid())
				.set("external_userid", tagBean.getExternalUserid())
				.set("tags", tagBean.getTags())
				.set("data", errMsg);
		Db.insert(getTenantDs(tagBean.getAppId()), tagMarkLog);
	}

	/**
	 * 返回应用的所属租户库数据源
	 * @param appId
	 * @return
	 */
	private static DataSource getTenantDs(String appId) {
		Entity entity = RedisUtil.get(CacheConstants.KEY_APP + appId);
		return DbConfig.tenantDSMap.get(entity.getLong("tenant_id"));
	}
}